home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 7684 / 7684.xpi / chrome / firefm.jar / content / fmUIState.js < prev    next >
Text File  |  2009-09-14  |  36KB  |  1,073 lines

  1. /**
  2.  * Copyright (c) 2008, Jose Enrique Bolanos, Jorge Villalobos
  3.  * All rights reserved.
  4.  *
  5.  * Redistribution and use in source and binary forms, with or without
  6.  * modification, are permitted provided that the following conditions are met:
  7.  *
  8.  *  * Redistributions of source code must retain the above copyright notice,
  9.  *    this list of conditions and the following disclaimer.
  10.  *  * Redistributions in binary form must reproduce the above copyright notice,
  11.  *    this list of conditions and the following disclaimer in the documentation
  12.  *    and/or other materials provided with the distribution.
  13.  *  * Neither the name of Jose Enrique Bolanos, Jorge Villalobos nor the names
  14.  *    of its contributors may be used to endorse or promote products derived
  15.  *    from this software without specific prior written permission.
  16.  *
  17.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  18.  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  19.  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  20.  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
  21.  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  22.  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  23.  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  24.  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  25.  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  26.  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  27.  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28.  **/
  29.  
  30. /**
  31.  * This object manages the state of all buttons and other elements that are
  32.  * visible to the user. We have complex state transitions, such as station
  33.  * loading, actual playback, login/logout, and online/offline.
  34.  */
  35. FireFMChrome.UIState = {
  36.   /* Observer topics this object listens to. */
  37.   _TOPICS :
  38.     [ FireFM.Station.TOPIC_STATION_SET,
  39.       FireFM.Station.TOPIC_STATION_SEARCHING,
  40.       FireFM.Station.TOPIC_STATION_LOADING,
  41.       FireFM.Station.TOPIC_STATION_OPENING,
  42.       FireFM.Station.TOPIC_STATION_STOPPING,
  43.       FireFM.Station.TOPIC_STATION_ERROR,
  44.       FireFM.Player.TOPIC_PLAYER_READY,
  45.       FireFM.Player.TOPIC_TRACK_LOADED,
  46.       FireFM.Player.TOPIC_PROGRESS_CHANGED,
  47.       FireFM.Player.TOPIC_PLAYER_ERROR,
  48.       FireFM.Playlist.TOPIC_TRACK_VIDEO_LOADED,
  49.       FireFM.Login.TOPIC_USER_AUTHENTICATION,
  50.       FireFM.Remote.TOPIC_TRACK_LOVED ],
  51.  
  52.   /* Timeout before running autoplay. */
  53.   _AUTOPLAY_TIMEOUT : 5 * 1000, // 5 seconds.
  54.  
  55.   /* Logger for this object. */
  56.   _logger : null,
  57.   /* String bundle in the overlay. */
  58.   _bundle : null,
  59.   /* Online/offline application state. */
  60.   _onLine : false,
  61.   /* Indicates if the player is ready. */
  62.   _isPlayerReady : false,
  63.   /* Indicates if a station is being searched for or not. */
  64.   _stationSearching : false,
  65.   /* Indicates if a station is loading or not. */
  66.   _stationLoading : false,
  67.   /* Indicates if the station is playing or not. */
  68.   _stationPlaying : false,
  69.   /* The currently logged in user. null if logged out. */
  70.   _currentUser : null,
  71.   /* Indicates whether the current track has been loved or not. */
  72.   _loved : false,
  73.   /* Volume preference. */
  74.   _volumePref : null,
  75.   /* Reference to the volume change listener. */
  76.   _volumeListener : null,
  77.   /* Status bar visibility preference. */
  78.   _statusbarPref : null,
  79.   /* Reference to the status bar visibility change listener. */
  80.   _statusbarListener : null,
  81.   /* Status bar buttons preference. */
  82.   _statusbarButtonsPref : null,
  83.   /* Reference to the status bar buttons change listener. */
  84.   _statusbarButtonsListener : null,
  85.   /* The Now Playing broadcaster, which is frequently used. */
  86.   _nowPlaying : null,
  87.   /* The time left menu item, which is frequently used. */
  88.   _timeLeftMenu : null,
  89.  
  90.   /**
  91.    * Initializes the object.
  92.    */
  93.   init : function() {
  94.     let that = this;
  95.  
  96.     this._logger = FireFM.getLogger("FireFMChrome.UIState");
  97.     this._logger.debug("init");
  98.  
  99.     // get the string bundle.
  100.     this._bundle = document.getElementById("firefm-string-bundle");
  101.  
  102.     // get the Now Playing broadcaster and time left menu item.
  103.     this._nowPlaying =
  104.       document.getElementById("firefm-now-playing-broadcaster");
  105.     this._timeLeftMenu =
  106.       document.getElementById("firefm-menu-playing-time-left");
  107.  
  108.     // set all preference objects and listeners.
  109.     this._volumePref =
  110.       Application.prefs.get(FireFM.PREF_BRANCH + "volumeLevel");
  111.     this._volumeListener =
  112.       function(aEvent) { that._setVolume(that._volumePref.value); };
  113.     this._statusbarPref =
  114.       Application.prefs.get(FireFM.PREF_BRANCH + "showInStatusBar");
  115.     this._statusbarListener =
  116.       function(aEvent) { that._toggleStatusBar(that._statusbarPref.value); };
  117.     this._statusbarButtonsPref =
  118.       Application.prefs.get(FireFM.PREF_BRANCH + "statusBarButtons");
  119.     this._statusbarButtonsListener =
  120.       function(aEvent) {
  121.         that._customizeStatusBar(that._statusbarButtonsPref.value); };
  122.  
  123.     // add observers.
  124.     for (let i = 0; i < this._TOPICS.length; i++) {
  125.       FireFM.obsService.addObserver(this, this._TOPICS[i], false);
  126.     }
  127.  
  128.     this._volumePref.events.addListener("change", this._volumeListener);
  129.     this._statusbarPref.events.addListener("change", this._statusbarListener);
  130.     this._statusbarButtonsPref.events.addListener(
  131.       "change", this._statusbarButtonsListener);
  132.  
  133.     // gather state information.
  134.     this._onLine = window.navigator.onLine;
  135.     this._isPlayerReady = (FireFM.Player.STATUS_READY == FireFM.Player.status);
  136.     this._stationPlaying = FireFM.Player.isPlaying;
  137.     this._stationLoading = FireFM.Station.isLoadingStation;
  138.     this._currentUser = FireFM.Login.userName;
  139.     this._setVolume(this._volumePref.value);
  140.     this._toggleStatusBar(this._statusbarPref.value);
  141.     this._customizeStatusBar(this._statusbarButtonsPref.value);
  142.  
  143.     // this only needs to be run for the first window being opened.
  144.     if (!FireFM.startupDone) {
  145.       if (0 < FireFM.History.stationHistory.length) {
  146.         let lastStation = FireFM.History.stationHistory[0];
  147.  
  148.         // try autoplay after a bit.
  149.         window.setTimeout(
  150.           function() {
  151.             try {
  152.               FireFM.Station.setStation(lastStation.id, lastStation.type);
  153.               that._setPlayerReady();
  154.             } catch (e) {
  155.               that._logger.error(
  156.                 "init. Error setting station from preference:\n" + e);
  157.             }
  158.           }, this._AUTOPLAY_TIMEOUT);
  159.       }
  160.     }
  161.  
  162.     this._setStationButtonFirstRun();
  163.     this._setPreferencesCommand();
  164.     // finally, refresh the state of the whole UI.
  165.     this.refreshState();
  166.  
  167.     // set the now playing info for new windows.
  168.     if (FireFM.Player.isPlaying) {
  169.       this._setNowPlayingInfo(FireFM.Playlist.currentTrack);
  170.     }
  171.   },
  172.  
  173.   /**
  174.    * Unloads the object.
  175.    */
  176.   uninit : function() {
  177.     this._logger.debug("uninit");
  178.     // remove observers.
  179.     for (let i = 0; i < this._TOPICS.length; i++) {
  180.       FireFM.obsService.removeObserver(this, this._TOPICS[i]);
  181.     }
  182.  
  183.     this._volumePref.events.removeListener("change", this._volumeListener);
  184.     this._statusbarPref.events.removeListener(
  185.       "change", this._statusbarListener);
  186.   },
  187.  
  188.   /**
  189.    * Sets the player for autoplay if it is ready and the preferences are set.
  190.    */
  191.   _setPlayerReady : function() {
  192.     this._logger.trace("_setPlayerReady");
  193.  
  194.     if (this._isPlayerReady) {
  195.       let autoplay = Application.prefs.get(FireFM.PREF_BRANCH + "autoplay");
  196.       let autoplayNotified =
  197.         Application.prefs.get(FireFM.PREF_BRANCH + "autoplay.notified");
  198.  
  199.       try {
  200.         // set the volume.
  201.         FireFMChrome.BrowserOverlay.setVolume(this._volumePref.value, false);
  202.  
  203.         // show auto-play notification if it has never been shown
  204.         if (autoplayNotified && !autoplayNotified.value) {
  205.           this._showAutoplayNotification();
  206.  
  207.         // autoplay if set by the user
  208.         } else if (autoplay && autoplay.value &&
  209.                    (null != FireFM.Login.userName)) {
  210.           FireFM.Station.play();
  211.         }
  212.       } catch (e) {
  213.         this._logger.error("_setPlayerReady. Error setting up player:\n" + e);
  214.       }
  215.     }
  216.   },
  217.  
  218.   /**
  219.    * Shows the auto-play notification.
  220.    */
  221.   _showAutoplayNotification : function() {
  222.     this._logger.trace("_showAutoplayNotification");
  223.  
  224.     let nb = gBrowser.getNotificationBox();
  225.     let buttonYes = new Object();
  226.     let buttonNo = new Object();
  227.     let brand =
  228.       document.getElementById("bundle_brand").getString("brandShortName");
  229.  
  230.     buttonYes.label = this._bundle.getString("firefm.yes.label");
  231.     buttonYes.accessKey = this._bundle.getString("firefm.yes.accesskey");
  232.     buttonYes.popup = null;
  233.     buttonYes.callback = function() {
  234.       Application.prefs.
  235.         setValue(FireFM.PREF_BRANCH + "autoplay.notified", true);
  236.       Application.prefs.setValue(FireFM.PREF_BRANCH + "autoplay", true);
  237.       FireFM.Station.play();
  238.     };
  239.  
  240.     buttonNo.label = this._bundle.getString("firefm.no.label");
  241.     buttonNo.accessKey = this._bundle.getString("firefm.no.accesskey");
  242.     buttonNo.popup = null;
  243.     buttonNo.callback = function() {
  244.       Application.prefs.
  245.         setValue(FireFM.PREF_BRANCH + "autoplay.notified", true);
  246.       Application.prefs.setValue(FireFM.PREF_BRANCH + "autoplay", false);
  247.     };
  248.  
  249.     nb.appendNotification(
  250.       this._bundle.getFormattedString(
  251.         "firefm.autoplayNotification.label", [brand]),
  252.       "firefm-autoplay-notification", "chrome://firefm/skin/logo32.png",
  253.       nb.PRIORITY_INFO_MEDIUM, [ buttonYes, buttonNo ]);
  254.   },
  255.  
  256.   /**
  257.    * Sets the label and accesskey for the preferences command. This needs
  258.    * special attention because the label is platform-specific.
  259.    */
  260.   _setPreferencesCommand : function() {
  261.     this._logger.trace("_setPreferencesCommand");
  262.  
  263.     let prefCmd = document.getElementById("firefm-preferences-cmd");
  264.     let os = FireFM.getOperatingSystem();
  265.  
  266.     if ((FireFM.OS_WINDOWS == os) || (FireFM.OS_WINDOWS_VISTA == os)) {
  267.       prefCmd.setAttribute(
  268.         "label", this._bundle.getString("firefm.options.label"));
  269.       prefCmd.setAttribute(
  270.         "accesskey", this._bundle.getString("firefm.options.accesskey"));
  271.     } else {
  272.       prefCmd.setAttribute(
  273.         "label", this._bundle.getString("firefm.optionsUnix.label"));
  274.       prefCmd.setAttribute(
  275.         "accesskey", this._bundle.getString("firefm.optionsUnix.accesskey"));
  276.     }
  277.   },
  278.  
  279.   /**
  280.    * Refreshes the state of the UI components.
  281.    */
  282.   refreshState : function() {
  283.     this._logger.debug("refreshState");
  284.     this._refreshLoginBroadcaster();
  285.     this._refreshLoginButton();
  286.     this._refreshPlayButton();
  287.     this._refreshSkipButton();
  288.     this._refreshVolumeBroadcaster();
  289.     //this._refreshShareButton();
  290.     this._refreshTagButton();
  291.     this._refreshVideoButton();
  292.     this._refreshLoveButton();
  293.     this._refreshBanButton();
  294.     this._refreshRecentStationMenu();
  295.     this._refreshNowPlayingMenu();
  296.   },
  297.  
  298.   /**
  299.    * Refreshes the state of the login broadcaster.
  300.    */
  301.   _refreshLoginBroadcaster : function() {
  302.     this._logger.trace("_refreshLoginBroadcaster");
  303.  
  304.     let loginBroadcaster =
  305.       document.getElementById("firefm-logged-in-broadcaster");
  306.  
  307.     if (this._onLine && this._isPlayerReady && (null != this._currentUser)) {
  308.       loginBroadcaster.removeAttribute("disabled");
  309.     } else {
  310.       loginBroadcaster.setAttribute("disabled", true);
  311.     }
  312.   },
  313.  
  314.   /**
  315.    * Refreshes the state of the login / logout button.
  316.    */
  317.   _refreshLoginButton : function() {
  318.     this._logger.trace("_refreshLoginButton");
  319.  
  320.     let loginLogoutCmd = document.getElementById("firefm-login-logout-cmd");
  321.     let loginLogoutMenu = document.getElementById("firefm-login-logout-menu");
  322.  
  323.     loginLogoutCmd.removeAttribute("disabled");
  324.  
  325.     if (this._onLine && this._isPlayerReady) {
  326.       if (null != this._currentUser) {
  327.         let logoutStr =
  328.           this._bundle.getFormattedString(
  329.             "firefm.logout.label", [ this._currentUser ]);
  330.  
  331.         loginLogoutCmd.setAttribute("loggedin", true);
  332.         loginLogoutCmd.setAttribute("label", logoutStr);
  333.         loginLogoutCmd.tooltipText = logoutStr;
  334.  
  335.         if (null != loginLogoutMenu) {
  336.           loginLogoutMenu.setAttribute(
  337.             "accesskey", this._bundle.getString("firefm.logout.accesskey"));
  338.         }
  339.       } else {
  340.         let loginStr = this._bundle.getString("firefm.login.label");
  341.  
  342.         loginLogoutCmd.removeAttribute("loggedin");
  343.         loginLogoutCmd.setAttribute("label", loginStr);
  344.         loginLogoutCmd.tooltipText = loginStr;
  345.  
  346.         if (null != loginLogoutMenu) {
  347.           loginLogoutMenu.setAttribute(
  348.             "accesskey", this._bundle.getString("firefm.login.accesskey"));
  349.         }
  350.       }
  351.     } else {
  352.       loginLogoutCmd.removeAttribute("loggedin");
  353.       loginLogoutCmd.setAttribute(
  354.         "label", this._bundle.getString("firefm.login.label"));
  355.       loginLogoutCmd.tooltipText =
  356.         this._bundle.getString(
  357.           (!this._onLine ? "firefm.offline.label" :
  358.            "firefm.playerNotLoaded.label"));
  359.       loginLogoutCmd.setAttribute("disabled", true);
  360.  
  361.       if (null != loginLogoutMenu) {
  362.         loginLogoutMenu.setAttribute(
  363.           "accesskey", this._bundle.getString("firefm.login.accesskey"));
  364.       }
  365.     }
  366.   },
  367.  
  368.   /**
  369.    * Refreshes the state of the play / stop button.
  370.    */
  371.   _refreshPlayButton : function() {
  372.     this._logger.trace("_refreshPlayButton");
  373.  
  374.     let playStopCmd = document.getElementById("firefm-play-stop-cmd");
  375.     let playStopMenu = document.getElementById("firefm-play-stop-menu");
  376.  
  377.     playStopCmd.removeAttribute("disabled");
  378.  
  379.     if (this._onLine && this._isPlayerReady &&
  380.         (null != FireFM.Login.userName)) {
  381.       let station = FireFM.Station.station;
  382.  
  383.       if (this._stationPlaying || this._stationSearching ||
  384.           this._stationLoading) {
  385.         playStopCmd.setAttribute("playing", true);
  386.         playStopCmd.setAttribute(
  387.           "label", this._bundle.getString("firefm.stop.label"));
  388.  
  389.         if (this._stationPlaying) {
  390.           playStopCmd.tooltipText =
  391.             this._bundle.getFormattedString(
  392.               "firefm.stop.tooltip", [ station.title ]);
  393.         } else {
  394.           playStopCmd.tooltipText = this._bundle.getString("firefm.stop.label");
  395.         }
  396.  
  397.         if (null != playStopMenu) {
  398.           playStopMenu.setAttribute(
  399.             "accesskey", this._bundle.getString("firefm.stop.accesskey"));
  400.         }
  401.       } else {
  402.         playStopCmd.removeAttribute("playing");
  403.         playStopCmd.setAttribute(
  404.           "label", this._bundle.getString("firefm.play.label"));
  405.  
  406.         if (null != station) {
  407.           playStopCmd.tooltipText =
  408.             this._bundle.getFormattedString(
  409.               "firefm.play.tooltip", [ station.title ]);
  410.         } else {
  411.           playStopCmd.tooltipText =
  412.             this._bundle.getString("firefm.playDefault.tooltip");
  413.           playStopCmd.setAttribute("disabled", true);
  414.         }
  415.  
  416.         if (null != playStopMenu) {
  417.           playStopMenu.setAttribute(
  418.             "accesskey", this._bundle.getString("firefm.play.accesskey"));
  419.         }
  420.       }
  421.     } else {
  422.       playStopCmd.removeAttribute("playing");
  423.       playStopCmd.setAttribute(
  424.         "label", this._bundle.getString("firefm.play.label"));
  425.  
  426.       if (!this._onLine) {
  427.         playStopCmd.tooltipText =
  428.           this._bundle.getString("firefm.offline.label");
  429.       } else if (!this._isPlayerReady) {
  430.         playStopCmd.tooltipText =
  431.           this._bundle.getString("firefm.playerNotLoaded.label");
  432.       } else {
  433.         playStopCmd.tooltipText =
  434.           this._bundle.getString("firefm.needLogin.label");
  435.       }
  436.  
  437.       playStopCmd.setAttribute("disabled", true);
  438.  
  439.       if (null != playStopMenu) {
  440.         playStopMenu.setAttribute(
  441.           "accesskey", this._bundle.getString("firefm.play.accesskey"));
  442.       }
  443.     }
  444.   },
  445.  
  446.   /**
  447.    * Refreshes the state of the skip button.
  448.    */
  449.   _refreshSkipButton : function() {
  450.     this._logger.trace("_refreshSkipButton");
  451.  
  452.     let skipCmd = document.getElementById("firefm-skip-cmd");
  453.  
  454.     if (this._onLine && this._isPlayerReady) {
  455.       if (this._stationPlaying) {
  456.         skipCmd.removeAttribute("disabled");
  457.         skipCmd.tooltipText = this._bundle.getString("firefm.skip.tooltip");
  458.       } else {
  459.         skipCmd.setAttribute("disabled", true);
  460.         skipCmd.tooltipText = this._bundle.getString("firefm.skip.tooltip");
  461.       }
  462.     } else {
  463.       skipCmd.setAttribute("disabled", true);
  464.       skipCmd.tooltipText = this._bundle.getString("firefm.skip.tooltip");
  465.     }
  466.   },
  467.  
  468.   /**
  469.    * Refreshes the state of the volume broadcaster.
  470.    */
  471.   _refreshVolumeBroadcaster : function() {
  472.     this._logger.trace("_refreshVolumeBroadcaster");
  473.  
  474.     let volumeBroadcaster =
  475.       document.getElementById("firefm-volume-broadcaster");
  476.  
  477.     if (this._isPlayerReady) {
  478.       volumeBroadcaster.removeAttribute("disabled");
  479.     } else {
  480.       volumeBroadcaster.setAttribute("disabled", true);
  481.     }
  482.   },
  483.  
  484.   /**
  485.    * Refreshes the state of the share track button.
  486.    */
  487.   _refreshShareButton : function() {
  488.     this._logger.trace("_refreshShareButton");
  489.  
  490.     let shareCmd = document.getElementById("firefm-share-cmd");
  491.  
  492.     shareCmd.setAttribute("disabled", true);
  493.  
  494.     if (this._onLine && this._isPlayerReady && (null != this._currentUser)) {
  495.       shareCmd.tooltipText = this._bundle.getString("firefm.share.tooltip");
  496.  
  497.       if (this._stationPlaying) {
  498.         shareCmd.removeAttribute("disabled");
  499.       }
  500.     } else {
  501.       shareCmd.tooltipText = this._bundle.getString("firefm.needLogin.label");
  502.     }
  503.   },
  504.  
  505.   /**
  506.    * Refreshes the state of the tag track button.
  507.    */
  508.   _refreshTagButton : function() {
  509.     this._logger.trace("_refreshTagButton");
  510.  
  511.     let tagCmd = document.getElementById("firefm-tag-cmd");
  512.  
  513.     tagCmd.setAttribute("disabled", true);
  514.  
  515.     if (this._onLine && this._isPlayerReady && (null != this._currentUser)) {
  516.       tagCmd.tooltipText = this._bundle.getString("firefm.tag.tooltip");
  517.  
  518.       if (this._stationPlaying && !FireFM.Private.isPrivate) {
  519.         tagCmd.removeAttribute("disabled");
  520.       }
  521.     } else {
  522.       tagCmd.tooltipText = this._bundle.getString("firefm.needLogin.label");
  523.     }
  524.   },
  525.  
  526.   /**
  527.    * Refreshes the state of the love track button.
  528.    */
  529.   _refreshLoveButton : function() {
  530.     this._logger.trace("_refreshLoveButton");
  531.     // oh yeah, the love command :P
  532.     let loveCmd = document.getElementById("firefm-love-cmd");
  533.  
  534.     loveCmd.setAttribute("disabled", true);
  535.  
  536.     if (this._onLine && this._isPlayerReady && (null != this._currentUser)) {
  537.       if (this._stationPlaying && !FireFM.Private.isPrivate) {
  538.         if (!this._loved) {
  539.           if (!this._lovedError) {
  540.             loveCmd.tooltipText = this._bundle.getString("firefm.love.tooltip");
  541.           } else {
  542.             loveCmd.tooltipText =
  543.               this._bundle.getString("firefm.lovedError.tooltip");
  544.           }
  545.  
  546.           loveCmd.removeAttribute("disabled");
  547.         } else {
  548.           loveCmd.tooltipText =
  549.             this._bundle.getString("firefm.alreadyLoved.tooltip");
  550.         }
  551.       } else {
  552.         loveCmd.tooltipText = this._bundle.getString("firefm.love.tooltip");
  553.       }
  554.     } else {
  555.       loveCmd.tooltipText = this._bundle.getString("firefm.needLogin.label");
  556.     }
  557.   },
  558.  
  559.   /**
  560.    * Refreshes the state of the ban track button.
  561.    */
  562.   _refreshBanButton : function() {
  563.     this._logger.trace("_refreshBanButton");
  564.  
  565.     let banCmd = document.getElementById("firefm-ban-cmd");
  566.  
  567.     banCmd.setAttribute("disabled", true);
  568.  
  569.     if (this._onLine && this._isPlayerReady && (null != this._currentUser)) {
  570.       banCmd.tooltipText = this._bundle.getString("firefm.ban.tooltip");
  571.  
  572.       if (this._stationPlaying && !FireFM.Private.isPrivate) {
  573.         banCmd.removeAttribute("disabled");
  574.       }
  575.     } else {
  576.       banCmd.tooltipText = this._bundle.getString("firefm.needLogin.label");
  577.     }
  578.   },
  579.  
  580.   /**
  581.    * Refreshes the state of the video button.
  582.    */
  583.   _refreshVideoButton : function() {
  584.     this._logger.trace("_refreshVideoButton");
  585.  
  586.     let currentTrack = FireFM.Playlist.currentTrack;
  587.     let videoCmd = document.getElementById("firefm-video-cmd");
  588.  
  589.     videoCmd.setAttribute("disabled", true);
  590.  
  591.     // TODO: Change tooltip depending on whether the video is available or not?
  592.     if (this._stationPlaying &&
  593.         null != currentTrack && null != currentTrack.video) {
  594.  
  595.       videoCmd.removeAttribute("disabled");
  596.     }
  597.   },
  598.  
  599.   /**
  600.    * Refreshes the state of the recent stations menu.
  601.    */
  602.   _refreshRecentStationMenu : function() {
  603.     this._logger.trace("_refreshRecentStationMenu");
  604.  
  605.     let recentCmd = document.getElementById("firefm-recent-station-cmd");
  606.  
  607.     if (this._onLine && this._isPlayerReady) {
  608.       recentCmd.removeAttribute("disabled");
  609.     } else {
  610.       recentCmd.setAttribute("disabled", true);
  611.     }
  612.   },
  613.  
  614.   /**
  615.    * Refreshes the state of the Now Playing menu.
  616.    */
  617.   _refreshNowPlayingMenu : function() {
  618.     this._logger.trace("_refreshNowPlayingMenu");
  619.  
  620.     let nowPlaying = document.getElementById("firefm-menu-now-playing");
  621.  
  622.     if (null != nowPlaying) {
  623.       if (this._onLine && this._isPlayerReady && this._stationPlaying) {
  624.         nowPlaying.removeAttribute("disabled");
  625.       } else {
  626.         nowPlaying.setAttribute("disabled", true);
  627.       }
  628.     }
  629.   },
  630.  
  631.   /**
  632.    * Sets the Now Playing information with the given track.
  633.    * @param aTrack the track to use to set the information.
  634.    */
  635.   _setNowPlayingInfo : function(aTrack) {
  636.     this._logger.trace("_setNowPlayingInfo");
  637.  
  638.     let infoMenuItem = document.getElementById("firefm-menu-playing-info");
  639.     let trackMenuItem = document.getElementById("firefm-menu-playing-track");
  640.     let artistMenuItem = document.getElementById("firefm-menu-playing-artist");
  641.     let albumMenuItem = document.getElementById("firefm-menu-playing-album");
  642.     let freeMenuItem = document.getElementById("firefm-menu-playing-free");
  643.  
  644.     this._nowPlaying.setAttribute("tracktitle", aTrack.title);
  645.     this._nowPlaying.setAttribute("trackimage", aTrack.imagePath);
  646.     this._nowPlaying.setAttribute("trackurl", aTrack.trackURL);
  647.     this._nowPlaying.setAttribute("freetrackurl", aTrack.freeTrackURL);
  648.     this._nowPlaying.setAttribute("artist", aTrack.artist);
  649.     this._nowPlaying.setAttribute("artisturl", aTrack.artistURL);
  650.     this._nowPlaying.setAttribute("album", aTrack.albumTitle);
  651.     this._nowPlaying.setAttribute("albumurl", aTrack.albumURL);
  652.     this._nowPlaying.removeAttribute("message");
  653.     this._nowPlaying.removeAttribute("progress");
  654.  
  655.     if (null != infoMenuItem) {
  656.       let trackString = aTrack.title + " - " + aTrack.artist;
  657.  
  658.       infoMenuItem.setAttribute("label", trackString);
  659.       infoMenuItem.setAttribute("tooltiptext", trackString);
  660.  
  661.       // XXX: don't use icon images on Mac because they're buggy and
  662.       // causing crashes.
  663.       if (FireFM.OS_MAC != FireFM.getOperatingSystem()) {
  664.         infoMenuItem.setAttribute("image", aTrack.imagePath);
  665.       } else {
  666.         infoMenuItem.removeAttribute("class");
  667.       }
  668.     }
  669.  
  670.     if (null != trackMenuItem) {
  671.       if (0 < String(aTrack.trackURL).length) {
  672.         trackMenuItem.setAttribute("fmurl", aTrack.trackURL);
  673.         trackMenuItem.removeAttribute("disabled");
  674.       } else {
  675.         trackMenuItem.setAttribute("disabled", true);
  676.       }
  677.     }
  678.  
  679.     if (null != artistMenuItem) {
  680.       if (0 < String(aTrack.artistURL).length) {
  681.         artistMenuItem.setAttribute("fmurl", aTrack.artistURL);
  682.         artistMenuItem.removeAttribute("disabled");
  683.       } else {
  684.         artistMenuItem.setAttribute("disabled", true);
  685.       }
  686.     }
  687.  
  688.     if (null != albumMenuItem) {
  689.       if (0 < String(aTrack.albumURL).length) {
  690.         albumMenuItem.setAttribute("fmurl", aTrack.albumURL);
  691.         albumMenuItem.removeAttribute("disabled");
  692.       } else {
  693.         albumMenuItem.setAttribute("disabled", true);
  694.       }
  695.     }
  696.  
  697.     if (null != freeMenuItem) {
  698.       if (0 < String(aTrack.freeTrackURL).length) {
  699.         freeMenuItem.setAttribute("fmurl", aTrack.freeTrackURL);
  700.         freeMenuItem.removeAttribute("disabled");
  701.       } else {
  702.         freeMenuItem.setAttribute("disabled", true);
  703.       }
  704.     }
  705.   },
  706.  
  707.   /**
  708.    * Clearss the Now Playing information.
  709.    */
  710.   _clearNowPlayingInfo : function() {
  711.     this._logger.trace("_clearNowPlayingInfo");
  712.     this._nowPlaying.removeAttribute("trackimage");
  713.     this._nowPlaying.removeAttribute("tracktitle");
  714.     this._nowPlaying.removeAttribute("trackurl");
  715.     this._nowPlaying.removeAttribute("artist");
  716.     this._nowPlaying.removeAttribute("artisturl");
  717.     this._nowPlaying.removeAttribute("album");
  718.     this._nowPlaying.removeAttribute("albumurl");
  719.   },
  720.  
  721.   /**
  722.    * Sets the current online state.
  723.    * @param aIsOnline whether the application is online or not. true if the
  724.    * application is online, false otherwise.
  725.    */
  726.   _setOnlineState : function(aIsOnline) {
  727.     this._logger.trace("_setOnlineState");
  728.     this._onLine = aIsOnline;
  729.  
  730.     if (!aIsOnline && this._stationPlaying) {
  731.       FireFM.Station.stop();
  732.     }
  733.  
  734.     this.refreshState();
  735.   },
  736.  
  737.   /**
  738.    * Gets the volume Preference object.
  739.    * @return the volume Preference object.
  740.    */
  741.   get volumePref() {
  742.     // XXX: there is no logging here for performance reasons.
  743.     return this._volumePref;
  744.   },
  745.  
  746.   /**
  747.    * Sets the volume in the volume scale.
  748.    * @param aLevel the volume level in a scale from 0 to 100.
  749.    */
  750.   _setVolume : function(aLevel) {
  751.     // XXX: there is no logging here for performance reasons.
  752.     let volumeBroadcaster =
  753.       document.getElementById("firefm-volume-broadcaster");
  754.     let levelAttr = "high";
  755.  
  756.     if (aLevel == 0) {
  757.       levelAttr = "zero";
  758.     } else if (aLevel <= 50) {
  759.       levelAttr = "low";
  760.     }
  761.  
  762.     volumeBroadcaster.setAttribute("value", aLevel);
  763.     volumeBroadcaster.setAttribute("volume", levelAttr);
  764.   },
  765.  
  766.   /**
  767.    * Shows or hides the statusbar Fire.fm toolbar.
  768.    * @param aShouldShow true if the statusbar toolbar should be shown, false
  769.    * otherwise.
  770.    */
  771.   _toggleStatusBar : function(aShouldShow) {
  772.     this._logger.trace("_toggleStatusBar");
  773.  
  774.     let panel = document.getElementById("firefm-statusbar-panel");
  775.  
  776.     if (null != panel) {
  777.       panel.hidden = !aShouldShow;
  778.     }
  779.   },
  780.  
  781.   /**
  782.    * Customizes the Fire.fm statusbar with the current value of the buttons
  783.    * preference.
  784.    * @param aButtons string with the list of button ids as they should be shown.
  785.    */
  786.   _customizeStatusBar : function(aButtons) {
  787.     this._logger.trace("_customizeStatusBar");
  788.  
  789.     let toolbar = document.getElementById("firefm-statusbar-toolbar");
  790.     let toolbarButtons = toolbar.childNodes;
  791.     let toolbarButton;
  792.     let buttonRE;
  793.     let buttonMatch;
  794.  
  795.     for (let i = 0; i < toolbarButtons.length; i++) {
  796.       toolbarButton = toolbarButtons[i];
  797.  
  798.       if (!toolbarButton.id.match(/firefm/gi)) {
  799.         //XXX: Elements that may have been added to Fire.fm's status bar toolbar
  800.         //are ignored, eg. Foxytunes elements.
  801.         continue;
  802.       }
  803.  
  804.       buttonRE =
  805.         new RegExp("(?:^|\\,)" + toolbarButton.id + "(\\[[a-z]+\\])?(?:\\,|$)");
  806.       buttonMatch = buttonRE.exec(aButtons);
  807.  
  808.       if (null != buttonMatch) {
  809.         toolbarButton.hidden = false;
  810.  
  811.         if ("firefm-status-track-info" == toolbarButton.id) {
  812.           if ((1 < buttonMatch.length) && ("[large]" == buttonMatch[1])) {
  813.             toolbarButton.firstChild.removeAttribute("small");
  814.           } else {
  815.             toolbarButton.firstChild.setAttribute("small", true);
  816.           }
  817.         }
  818.       } else {
  819.         toolbarButton.hidden = true;
  820.       }
  821.     }
  822.   },
  823.  
  824.   /**
  825.    * Sets a different appearance for the station button the first time the
  826.    * extension runs.
  827.    */
  828.   _setStationButtonFirstRun : function() {
  829.     this._logger.trace("_setStationButtonFirstRun");
  830.  
  831.     let extension = Application.extensions.get(FireFM.EXTENSION_UUID);
  832.  
  833.     if (extension.firstRun && (0 == FireFM.History.stationHistory.length)) {
  834.       let historyPref =
  835.         Application.prefs.get(FireFM.PREF_BRANCH + "recent.history");
  836.       let stationButton = document.getElementById("firefm-station-button");
  837.  
  838.       // TODO: when we implement a startup wizard, we can re-enable this after
  839.       // it's finished.
  840.       //stationButton.setAttribute("firstRun", true);
  841.       //historyPref.events.addListener("change",
  842.       //  function(aEvent) { stationButton.setAttribute("firstRun", false); });
  843.     }
  844.   },
  845.  
  846.   /**
  847.    * Sets the given message in the Now Playing observer.
  848.    * @param aMessageKey the message key that holds the message. If null, the
  849.    * message is cleared.
  850.    * @param aParameter (optional) the parameter added to the message.
  851.    */
  852.   _setNowPlayingMessage : function(aMessageKey, aParameter) {
  853.     this._logger.trace("_setNowPlayingMessage");
  854.  
  855.     if (null != aMessageKey) {
  856.       if (aParameter) {
  857.         this._nowPlaying.setAttribute(
  858.           "message",
  859.           this._bundle.getFormattedString(aMessageKey, [ aParameter ]));
  860.       } else {
  861.         this._nowPlaying.setAttribute(
  862.           "message", this._bundle.getString(aMessageKey));
  863.       }
  864.     } else {
  865.       this._nowPlaying.removeAttribute("message");
  866.     }
  867.   },
  868.  
  869.   /**
  870.    * Displays a message or window to the user informing about the Player error
  871.    * that occurred.
  872.    * @param aPlayerStatus the player error status.
  873.    */
  874.   _handlePlayerError : function(aPlayerStatus) {
  875.     this._logger.error("_handlePlayerError. Status: " + aPlayerStatus);
  876.  
  877.     this._setNowPlayingMessage("firefm.playerNotLoaded.label");
  878.  
  879.     if (FireFM.Player.STATUS_PLUGIN_MISSING == aPlayerStatus) {
  880.       // show a notification in the browser area, telling the user that the
  881.       // Flash plugin is not present or not up to date.
  882.       let nb = gBrowser.getNotificationBox();
  883.       let installButton = new Object();
  884.  
  885.       installButton.label =
  886.         this._bundle.getString("firefm.pluginNotification.installPlugin.label");
  887.       installButton.accessKey =
  888.         this._bundle.getString(
  889.           "firefm.pluginNotification.installPlugin.accesskey");
  890.       installButton.popup = null;
  891.       installButton.callback =
  892.         function() {
  893.           openUILinkIn("http://www.adobe.com/go/getflashplayer", "tab"); };
  894.  
  895.       nb.appendNotification(
  896.         this._bundle.getString("firefm.pluginNotification.label"),
  897.         "firefm-plugin-notification", "chrome://firefm/skin/logo32.png",
  898.         nb.PRIORITY_WARNING_HIGH, [ installButton ] );
  899.  
  900.     } else if (FireFM.Player.STATUS_LOAD_FAILED == aPlayerStatus) {
  901.       // show a notification in the browser area, telling the user something is
  902.       // preventing Fire.fm from loading tracks
  903.       let nb = gBrowser.getNotificationBox();
  904.       let learnMoreButton = new Object();
  905.  
  906.       learnMoreButton.label = this._bundle.getString("firefm.learnMore.label");
  907.       learnMoreButton.accessKey =
  908.         this._bundle.getString("firefm.learnMore.accesskey");
  909.       learnMoreButton.popup = null;
  910.       learnMoreButton.callback =
  911.         function() {
  912.           openUILinkIn(
  913.             "http://firefm.sourceforge.net/help/#unabletocontact", "tab"); };
  914.  
  915.       nb.appendNotification(
  916.         this._bundle.getString("firefm.connectionNotification.label"),
  917.         "firefm-blocked-notification", "chrome://firefm/skin/logo32.png",
  918.         nb.PRIORITY_WARNING_HIGH, [ learnMoreButton ]);
  919.  
  920.       this._setNowPlayingMessage("firefm.error.2.label");
  921.     } else {
  922.       // open an error reporting window that lets the user send us the error
  923.       // that caused the player not to load.
  924.       window.open(
  925.         "chrome://firefm/content/fmErrorDialog.xul",
  926.         "firefm-error-dialog",
  927.        "chrome,dialog,centerscreen").focus();
  928.     }
  929.   },
  930.  
  931.   /**
  932.    * Observes topic notifications.
  933.    * @param aSubject The object that experienced the change.
  934.    * @param aTopic The topic being observed.
  935.    * @param aData The data relating to the change.
  936.    */
  937.   observe : function(aSubject, aTopic, aData) {
  938.     // XXX: there is no logging here for performance purposes.
  939.     switch (aTopic) {
  940.  
  941.       case FireFM.Station.TOPIC_STATION_SEARCHING:
  942.         this._stationSearching = true;
  943.         this.refreshState();
  944.         this._setNowPlayingMessage(
  945.           "firefm.searching.label", FireFM.decodeFMString(aData));
  946.         break;
  947.  
  948.       case FireFM.Station.TOPIC_STATION_SET:
  949.         this.refreshState();
  950.         this._setNowPlayingMessage(
  951.           "firefm.stoppedStation.label",
  952.           decodeURIComponent(aSubject.wrappedJSObject.title));
  953.         break;
  954.  
  955.       case FireFM.Station.TOPIC_STATION_LOADING:
  956.         this._stationLoading = true;
  957.         this._stationSearching = false;
  958.         this.refreshState();
  959.         this._setNowPlayingMessage(
  960.           "firefm.loadingStation.label",
  961.           decodeURIComponent(aSubject.wrappedJSObject.title));
  962.         break;
  963.  
  964.       case FireFM.Station.TOPIC_STATION_OPENING:
  965.         this._stationPlaying = true;
  966.         this._stationLoading = false;
  967.         this.refreshState();
  968.         this._setNowPlayingMessage(
  969.           "firefm.openingStation.label",
  970.           decodeURIComponent(aSubject.wrappedJSObject.title));
  971.         break;
  972.  
  973.       case FireFM.Station.TOPIC_STATION_STOPPING:
  974.         this._stationPlaying = false;
  975.         this._stationLoading = false;
  976.         this._stationSearching = false;
  977.         this.refreshState();
  978.         this._clearNowPlayingInfo();
  979.         this._setNowPlayingMessage(
  980.           "firefm.stoppedStation.label",
  981.           decodeURIComponent(aSubject.wrappedJSObject.title));
  982.         break;
  983.  
  984.       case FireFM.Station.TOPIC_STATION_ERROR:
  985.         this._setNowPlayingMessage("firefm.error." + aData + ".label");
  986.  
  987.         this._stationLoading = false;
  988.         this._stationSearching = false;
  989.         this.refreshState();
  990.  
  991.         if (this._stationPlaying &&
  992.             (FireFM.Station.ERROR_NOT_FOUND == parseInt(aData))) {
  993.           let that = this;
  994.           // clear the message, so that the current station can continue
  995.           // showing its information.
  996.           window.setTimeout(
  997.             function() { that._setNowPlayingMessage(null); }, 5000);
  998.         }
  999.         break;
  1000.       case FireFM.Player.TOPIC_PLAYER_READY:
  1001.         this._isPlayerReady =
  1002.           (FireFM.Player.STATUS_READY == FireFM.Player.status);
  1003.         this.refreshState();
  1004.         break;
  1005.  
  1006.       case FireFM.Player.TOPIC_TRACK_LOADED:
  1007.         this._loved = false;
  1008.         this._lovedError = false;
  1009.         this.refreshState();
  1010.         this._setNowPlayingInfo(aSubject.wrappedJSObject);
  1011.         break;
  1012.  
  1013.       case FireFM.Player.TOPIC_PROGRESS_CHANGED:
  1014.         let remaining = FireFM.Player.remainingTime;
  1015.  
  1016.         this._nowPlaying.setAttribute("progress", aData);
  1017.         this._nowPlaying.setAttribute("remainingtime", "-" + remaining);
  1018.         this._nowPlaying.setAttribute("isbuffering", FireFM.Player.isBuffering);
  1019.  
  1020.         if (null != this._timeLeftMenu) {
  1021.           this._timeLeftMenu.setAttribute(
  1022.             "label",
  1023.             this._bundle.getFormattedString(
  1024.               "firefm.timeLeft.label", [ remaining ]));
  1025.         }
  1026.         break;
  1027.  
  1028.       case FireFM.Playlist.TOPIC_TRACK_VIDEO_LOADED:
  1029.         this._refreshVideoButton();
  1030.         break;
  1031.  
  1032.       case FireFM.Login.TOPIC_USER_AUTHENTICATION:
  1033.         if (null != aData) {
  1034.           this._logger.debug("observe. Logged in as: " + aData);
  1035.           this._currentUser = aData;
  1036.           this.refreshState();
  1037.         } else {
  1038.           this._logger.debug("observe. Logged out.");
  1039.           this._currentUser = null;
  1040.           this.refreshState();
  1041.         }
  1042.  
  1043.         break;
  1044.  
  1045.       case FireFM.Remote.TOPIC_TRACK_LOVED:
  1046.         if (null == aData) {
  1047.           this._loved = true;
  1048.           this.refreshState();
  1049.         } else if ("false" == aData) {
  1050.           this._lovedError = true;
  1051.           this._loved = false;
  1052.           this.refreshState();
  1053.         }
  1054.         break;
  1055.  
  1056.       case FireFM.Player.TOPIC_PLAYER_ERROR:
  1057.         if (FireFM.Player.STATUS_LOAD_FAILED != FireFM.Player.status) {
  1058.           this._isPlayerReady = false;
  1059.           this.refreshState();
  1060.         }
  1061.  
  1062.         this._handlePlayerError(FireFM.Player.status);
  1063.         break;
  1064.     }
  1065.   }
  1066. };
  1067.  
  1068. window.addEventListener(
  1069.   "online", function() { FireFMChrome.UIState._setOnlineState(true); }, false);
  1070. window.addEventListener(
  1071.   "offline", function() { FireFMChrome.UIState._setOnlineState(false); },
  1072.   false);
  1073.